#ifndef SPIN_HPP_
#define SPIN_HPP_

#include <cassert>
#include <iostream>
#include <sstream>

class Spin {

private:
  // data members:
  int _numer; ///< numerator
  int _denom; ///< denominator

  // private functions:

  /// Copy @a spin data members
  void _Copy(const Spin &__spin){
    _numer = __spin._numer;
    _denom = __spin._denom;
  }

  /// Build a rational spin from a double
  void _BuildFromDouble(double __x){
    int x_int = (int)__x;
    if(fabs(__x - x_int) < 1.e-5) { // integer spin
      _numer = x_int;
      _denom = 1;
    }
    else{
      double x = 2.*__x;
      x_int = (int)x;
      if(fabs(x - x_int) < 1.e-5) { // half-integer spin
	_numer = x_int;
	_denom = 2;
      }
      else{ // illegal assignment attempt
	std::stringstream ss;
	ss << "Attempt to assign a Spin object the value " << __x
	   << " which is neither integral nor half-integral.";
	throw std::out_of_range(ss.str().c_str());
      }
    }
  }

public:
  
  // create/copy/destroy:

  /// Default Constructor (spin = 0)
  Spin(){
    _numer = 0;
    _denom = 1;
  }

  /** Default Constructor (spin = @a numerator/denominator)
   * Note: @a denominator must be either 1 or 2 
   */
  Spin(int __numerator,int __denominator = 1){
    if(__denominator != 1 && __denominator != 2){
      std::stringstream ss;	
      ss << "Attempt to create a spin object with denominator = " 
	 << __denominator << ". Must be either 1 or 2.";
      throw std::out_of_range(ss.str().c_str());
    }
    assert(__denominator == 1 || __denominator == 2);
    _numer = __numerator;
    _denom = __denominator;
  }

  /// Constructor (@a spin must be a valid integral or half-integral spin)
  Spin(double __spin){
    this->_BuildFromDouble(__spin);
  }

  /// Copy Constructor
  Spin(const Spin &__spin){
    this->_Copy(__spin);
  }

  /// Destructor
  virtual ~Spin(){}

  // Getters:

  /// Returns the numerator of the spin
  inline int Numerator() const {
    return _numer;
  }

  /// Returns the denominator of the spin
  inline int Denominator() const {
    return _denom;
  }

  /// Returns the value (as floating point) of the spin
  inline double Value() const {
    return  _numer/(double)_denom;
  }

  // Setters:

  /// Set the spin
  inline void SetSpin(int __numer,int __denom = 1){
    _numer = __numer;
    _denom = __denom;
  }

  // operators:

  /// Assignment operator
  inline Spin& operator=(const Spin &__spin){
    this->_Copy(__spin);
    return *this;
  }

  /// Assignment operator
  inline Spin& operator=(double __spin){
    this->_BuildFromDouble(__spin);
    return *this;
  }

  /// Conversion operator
  operator double () const {
    return _numer/(double)_denom;
  }

  /// Prefix step up incrementer
  inline Spin& operator++(){
    _numer += _denom;
    return *this;
  }
  
  /// Postfix step up incrementer
  inline Spin& operator++(int){
    return ++(*this);
  }

  /// Prefix step down incrementer
  inline Spin& operator--(){
    _numer -= _denom;
    return *this;
  }

  /// Postfix step down incrementer
  inline Spin& operator--(int){
    return --(*this);
  }
  
  /// Set @a this = @a this + @a spin
  inline Spin& operator+=(const Spin &__spin){
    *this = *this + __spin;
    return *this;
  }

  /// Set @a this = @a this - @a spin
  inline Spin& operator-=(const Spin &__spin){
    *this = *this - __spin;
    return *this;
  }

  std::string ToString() const {
    std::stringstream ss;
    ss << _numer;
    if(_denom != 1) ss << "/" << _denom;

    return ss.str();
  }

  /// Print spin value to @a os
  inline void Print(std::ostream &__os = std::cout) const {
    __os << _numer;
    if(_denom != 1) __os << "/" << _denom;
  }
};
//_____________________________________________________________________________

/// Overload of << for Spin class
inline std::ostream& operator<<(std::ostream &__os, const Spin &__spin){
  __spin.Print(__os);
  return __os;
}
//_____________________________________________________________________________


#endif //SPIN_HPP_
